package com.wy.sloth.redis.lock;

import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.TimeUnit;

import org.apache.commons.lang3.RandomUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;

import com.wy.sloth.config.SlothConfig;
import com.wy.sloth.core.SlothNameManager;
import com.wy.sloth.core.SlothStandardNameManager;
import com.wy.sloth.exception.SlothLockException;
import com.wy.sloth.exception.SlothTimeoutException;
import com.wy.sloth.util.CollectionUtils;
import com.wy.sloth.util.StringUtils;
import com.wy.sloth.util.ThreadUtils;
import com.wy.sloth.util.UUIDUtils;

/**
 * redis分布式锁操作模板类
 * 
 * @author wangyuan
 * @since 2019年4月11日
 */
public class RedisLockTemplate {
    
    private static final Logger logger = LoggerFactory.getLogger(RedisLockTemplate.class);
    
    /**
     * 默认等待redis锁的时间（毫秒）
     */
    private static final long DEFAULT_ACQUIRE_TIMEOUT = 3L;
    
    /**
     * redis锁的默认超时时间（毫秒）
     */
    private static final long DEFAULT_LOCK_TIMEOUT = 10 * 1000L;
    
    private SlothConfig slothConfig;
            
    private SlothNameManager slothNameManager;
    
    private RedisTemplate<String, String> redisTemplate;
    
    private ValueOperations<String, String> valueOperations;
    
    public RedisLockTemplate(SlothConfig slothConfig, RedisTemplate<String, String> redisTemplate){
        this.slothConfig = slothConfig;
        this.redisTemplate = redisTemplate;
        
        this.valueOperations = this.redisTemplate.opsForValue();
        this.slothNameManager = new SlothStandardNameManager(this.slothConfig);
    }
    
    /**
     * 执行redis中需要加锁的逻辑
     * @param needLockKeys
     * @param action
     * @param acquireTimeout
     * @param lockTimeout
     * @return
     */
    public <T> T execute(List<String> needLockKeys, LockCallbackHandler<T> action, long acquireTimeout, long lockTimeout){
        long startExecuteTimeMillis = System.currentTimeMillis();
        
        //获取锁
    	Map<String, String> identifiers = new LinkedHashMap<>();
        boolean acquireLockResult = false;
        int maxTrySize = (int) ((lockTimeout + 5000) / acquireTimeout + 1);
        int tryCount = 1;
        do {
        	logger.info("第{}次准备获取key（{}）的redis锁", tryCount++, needLockKeys);
        	acquireLockResult = acquireLock(identifiers, needLockKeys, acquireTimeout, lockTimeout);
        } while((!acquireLockResult) && --maxTrySize > 0);
        
        if(!acquireLockResult) {
        	throw new SlothTimeoutException("获取redis锁超时");
        } else {
        	logger.info("获取key（{}）的redis锁成功", needLockKeys);
        }
        
        //执行事务型业务逻辑
        try {
        	logger.info("开始执行事务型业务逻辑");
            return action.doInLock();
        } catch (Exception e) {
            throw new SlothLockException("执行事务型redis处理逻辑过程中出现错误，原因是：", e);
        } finally {
            //释放获取的锁
        	logger.info("准备释放key（{}）的redis锁", needLockKeys);
        	releaseLock(identifiers);
        	
        	logger.info("本次执行耗时：{}毫秒", System.currentTimeMillis() - startExecuteTimeMillis);
        }
    }
    
    /**
     * 执行redis中需要加锁的逻辑
     * @param needLockKeys
     * @param action
     * @return
     */
    public <T> T execute(List<String> needLockKeys, LockCallbackHandler<T> action){
    	return execute(needLockKeys, action, DEFAULT_ACQUIRE_TIMEOUT, DEFAULT_LOCK_TIMEOUT);
    }
    
    /**
     * 执行redis中需要加锁的逻辑
     * @param needLockKey
     * @param action
     * @param acquireTimeout
     * @param lockTimeout
     * @return
     */
    public <T> T execute(String needLockKey, LockCallbackHandler<T> action, long acquireTimeout, long lockTimeout){
    	List<String> needLockKeys = new ArrayList<>();
    	if(StringUtils.isNotEmpty(needLockKey)) {
    		needLockKeys.add(needLockKey);
    	}
    	
    	return execute(needLockKeys, action, acquireTimeout, lockTimeout);
    }
    
    /**
     * 执行redis中需要加锁的逻辑
     * @param needLockKey
     * @param action
     * @return
     */
    public <T> T execute(String needLockKey, LockCallbackHandler<T> action){
    	return execute(needLockKey, action, DEFAULT_ACQUIRE_TIMEOUT, DEFAULT_LOCK_TIMEOUT);
    }
    
    /**
     * redis键值加锁
     * @param identifiers 锁的值，不能为null，必须要传入一个空的map
     * @param needLockKeys 需要获取锁的key
     * @param acquireTimeout 等待锁时间，毫秒
     * @param lockTimeout 持有锁的时间，毫秒
     * @return
     */
    public boolean acquireLock(Map<String, String> identifiers, List<String> needLockKeys, long acquireTimeout, long lockTimeout){
        if(CollectionUtils.isNotEmpty(needLockKeys)){
            String identifier = null;
            for(String needLockKeyItem : needLockKeys){
                identifier = acquireLock(needLockKeyItem, acquireTimeout, lockTimeout);
                if(StringUtils.isEmpty(identifier)) {
                    //先释放获取到的锁，然后再重试获取锁
                	logger.info("获取key（{}）的redis锁失败，将先释放获取到的锁，然后再重试获取锁", needLockKeyItem);
                    releaseLock(identifiers);
                    identifiers.clear();
                    ThreadUtils.sleep(TimeUnit.MILLISECONDS, RandomUtils.nextLong(0, 8L));
                    return false;
                } else {
                    identifiers.put(needLockKeyItem, identifier);
                }
            }
        }
        
        return true;
    }
    
    
    
    /**
     * redis键值加锁
     * @param lockName
     * @param acquireTimeout 等待锁时间，毫秒级别
     * @param lockTimeout 持有锁的时间，毫秒级别
     * @return
     */
    public String acquireLock(String needLockKey, long acquireTimeout, long lockTimeout){
        String identifier = null;
        String lockName = null;
        try {
            lockName = slothNameManager.getLockKey(needLockKey);
            long end = System.currentTimeMillis() + acquireTimeout;
            while (System.currentTimeMillis() < end) {
                identifier = UUIDUtils.generateUUID();
                if(valueOperations.setIfAbsent(lockName, identifier)){
                    redisTemplate.expire(lockName, lockTimeout, TimeUnit.MILLISECONDS);
                    logger.info("获取key（{}）的redis锁成功", needLockKey);
                    break;
                } else if(redisTemplate.getExpire(lockName) == -1){
                	logger.info("key（{}）的redis锁未设置过期时间，将设置此key的锁的过期时间", needLockKey);
                    redisTemplate.expire(lockName, lockTimeout, TimeUnit.MILLISECONDS);
                }
                
                identifier = null;
                
                ThreadUtils.sleep(TimeUnit.MILLISECONDS, 1L);
            }
        } catch (Exception e) {
            logger.error("获取在key（" + needLockKey + "）上的redis锁时出现错误，原因是：", e);
            identifier = null;
        }
        
        return identifier;
    }
    
    /**
     * redis键值加锁
     * @param needLockKey
     * @param lockTimeout 等待锁时间、持有锁的时间，毫秒级别
     * @return
     */
    public String acquireLock(String needLockKey, long lockTimeout){
        return acquireLock(needLockKey, lockTimeout, lockTimeout);
    }
    
    /**
     * redis键值加锁
     * @param needLockKey
     * @return
     */
    public String acquireLock(String needLockKey){
        return acquireLock(needLockKey, DEFAULT_ACQUIRE_TIMEOUT, DEFAULT_LOCK_TIMEOUT);
    }
    
    /**
     * redis键值加锁
     * @param identifiers
     * @param needLockKeys
     * @return
     */
    public boolean acquireLock(Map<String, String> identifiers, List<String> needLockKeys){
        return acquireLock(identifiers, needLockKeys, DEFAULT_ACQUIRE_TIMEOUT, DEFAULT_LOCK_TIMEOUT);
    }
    
    /**
     * redis释放锁
     * @param needLockKey 释放的锁的key
     * @param identifier 锁的值
     * @return true：释放锁成功，false：释放锁失败，锁已经不存在了
     */
    public boolean releaseLock(String needLockKey, String identifier) {
        boolean releaseResult = true;
        try {
            String lockKeyName = slothNameManager.getLockKey(needLockKey);
            String existIdentifier = valueOperations.get(lockKeyName);
            if(StringUtils.isEmpty(existIdentifier)){
                //锁超时
            	logger.warn("释放key（{}）的redis锁超时", needLockKey);
                return false;
            }
            
            if(existIdentifier.equals(identifier)){
            	releaseResult = redisTemplate.delete(lockKeyName);
            	if(!releaseResult) {
            		logger.warn("释放key（{}）的redis锁失败，未成功删除", needLockKey);
            	} else {
            		logger.info("释放key（{}）的redis锁成功", needLockKey);
            	}
            } else {
                //锁超时或着是锁已经被其他线程获取了
            	releaseResult = false;
            	logger.warn("释放key（{}）的redis锁超时或此锁已经被其他线程获取", needLockKey);
            }
        } catch (Exception e) {
            logger.error("释放key（" + needLockKey + "）的redis锁时出现错误，原因是：", e);
            releaseResult = false;
        }
        
        return releaseResult;
    }
    
    /**
     * redis释放锁
     * @param identifiers
     * @return
     */
    public boolean releaseLock(Map<String, String> identifiers){
        if(CollectionUtils.isEmpty(identifiers)){
            return true;
        }
        
        boolean releaseResult = true;
        for(Entry<String, String> identifierItem : identifiers.entrySet()){
            if(!releaseLock(identifierItem.getKey(), identifierItem.getValue())){
                releaseResult = false;
            }
        }
        
        if(releaseResult) {
        	logger.info("释放key（{}）的redis锁成功", identifiers.keySet());
        } else {
        	logger.warn("释放key（{}）的redis锁失败", identifiers.keySet());
        }
        
        return releaseResult;
    }

}
